using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;

using Oarw.General.Text;

namespace ScreenMonkey.Plugin.Clips.Song
{
    public partial class WordsDisplay : UserControl, IWordsDisplay
    {
        private float fadePosition = 1;
        private int sectionIndex = 0;
        private Timer fadeTimer = new Timer();

        private EventHandler styleChangedCallback;

        public WordsDisplay()
        {
            InitializeComponent();

            fadeTimer.Interval = 25;
            fadeTimer.Tick += new EventHandler(fadeTimer_Tick);

            styleChangedCallback = new EventHandler(displayStyle_Changed);

        }

        void fadeTimer_Tick(object sender, EventArgs e)
        {
            Animate();
        }

        private void Animate()
        {
            if (DisplayStyle.TransitionTime <= 0)
                fadePosition = 1f;
            else
                fadePosition += 1f / ((float)DisplayStyle.TransitionTime / 25f);

            if (fadePosition >= 1)
            {
                fadePosition = 1;
                fadeTimer.Stop();
            }
            ReDraw();
        }

        private SongDisplayStyle displayStyle = new SongDisplayStyle();

        public SongDisplayStyle DisplayStyle
        {
            get { return displayStyle; }
            set
            {
                //if(displayStyle.Changed != null) displayStyle.Changed -= styleChangedCallback;
                displayStyle = value;
                displayStyle.Changed += styleChangedCallback;

                UpdateStyle();
            }
        }

        private void displayStyle_Changed(object sender, EventArgs e)
        {
            UpdateStyle();
            InvalidateBackground();
        }

        private String oldWords;
        private String currentWords;

        public String CurrentWords
        {
            get { return currentWords; }
            set
            {
                if (currentWords != value)
                {
                    oldWords = currentWords;
                    currentWords = value;

                    Fade();
                }
            }
        }

        private string copyright;

        public string Copyright
        {
            get { return copyright; }
            set { copyright = value; }
        }
	

        private StringFormat MainWordsFormat
        {
            get
            {
                StringFormat format = new StringFormat();

                format.Alignment = StringAlignment.Center;
                format.LineAlignment = StringAlignment.Center;

                return format;
            }
        }



        private String words = string.Empty;

        public String Words
        {
            get { return words; }
            set
            {
                if (words != value)
                {
                    words = value;
                    LayoutWords(value);
                    DisplaySection(0);
                }
            }
        }

        public void RefreshWordLayout()
        {
            LayoutWords(words);
            DisplaySection(sectionIndex);
        }

        private void UpdateStyle()
        {
            this.BackColor = DisplayStyle.BackgroundColour;
            if (File.Exists(DisplayStyle.BackgroundImage))
                this.BackgroundImage = new Bitmap(DisplayStyle.BackgroundImage);
            else
                this.BackgroundImage = null;

            Invalidate();
        }

        public void NextSection()
        {
            DisplaySection(sectionIndex + 1);
        }

        public void PreviousSection()
        {
            if (!FirstSection)
            {
                DisplaySection(sectionIndex - 1);
            }
        }

        private void DisplaySection(int index)
        {
            if (index >=0 && index < SectionCount)
            {
                sectionIndex = index;
                CurrentWords = wordSections[sectionIndex];
            }

        }

        public int SectionCount
        {
            get
            {
                return wordSections.Count;
            }
        }

        public int SectionIndex
        {
            get
            {
                return sectionIndex;
            }
            set
            {
                DisplaySection(value);
            }
        }

        public bool LastSection
        {
            get
            {
                return (SectionIndex + 1) >= SectionCount;
            }
        }

        public bool FirstSection
        {
            get
            {
                return SectionIndex ==0;
            }
        }


        List<string> wordSections = new List<string>();
        private void LayoutWords(String text)
        {
            //Clear all existing sections so we do not end up with sections from a previous verse.
            wordSections.Clear();
            
            //Split the words into sections that fit on the screen.
            //First split it into sub sections, a subsection is a section that has been forced by the user
            //to be on a seperate scree.
            foreach(string subSection in SubSections(text))
            {
                List<char> delimeters = new List<char>();
                delimeters.Add('\n');
                delimeters.Add('.');
                delimeters.Add(';');
                delimeters.Add(',');
                delimeters.Add(' ');

                wordSections.AddRange(DivideSection(subSection,delimeters));
            }

        }

        private List<string> DivideSection(string line, List<char> delimeters)
        {
            int sectionCount = 1;
            char delimeter = delimeters[0];
            delimeters.RemoveAt(0);

            //Divide this lines into sentances that fit on the screen.
            List<string> components = new List<string>();

            //We need to check whether one of the single lines is too big to fit.
            //If this is the case we should split the line up.
            foreach (string subComponent in line.Split(new char[] { delimeter }, StringSplitOptions.RemoveEmptyEntries))
            {
                if (!DoWordsFit(subComponent) && !(delimeters.Count == 0))
                {
                    //This line is too big so we need to split it.
                    components.AddRange(DivideSection(subComponent, delimeters));
                }
                else
                    components.Add(subComponent);
            }

            while (!DoSectionsFit(JoinSections(components, sectionCount, delimeter.ToString())) && sectionCount < components.Count)
            {
                sectionCount++;
            }

            return JoinSections(components, sectionCount, delimeter.ToString());
        }

        private string[] SubSections(string text)
        {
            //If there is no text then return an empty array.
            if (String.IsNullOrEmpty(text)) return new string[0];
            
            text = text.Trim(' ','\n');
            return text.Split(new string[] {"\n\n","\n\r"}, StringSplitOptions.RemoveEmptyEntries);
        }

        private List<String> JoinSections(List<String> lines, int sections, string seperator)
        {
            List<String> textSections = new List<string>();
            int sectionLength = (int)(lines.Count / sections);
            
            for (int n = 0; n < lines.Count; n+=sectionLength)
            {
                string[] section = new string[sectionLength];
                lines.CopyTo(n,section, 0, Math.Min(sectionLength,lines.Count-n));

                string append = (seperator == ".") ? ".":"";
                textSections.Add(string.Join(seperator,section) + append);
            }

            return textSections;
        }

        private bool DoSectionsFit(List<String> sections)
        {
            bool result = true;

            foreach (String item in sections)
            {
                result &= DoWordsFit(item);
            }

            return result;
        }

        private bool DoWordsFit(String text)
        {
            int sectionHeight;
            Graphics gText = this.CreateGraphics();
            RectangleF area = MainDisplayArea;

            sectionHeight = (int)gText.MeasureString(text, DisplayStyle.MainText.ScaledFont(this.Height), (int) area.Width, MainWordsFormat).Height;

            return sectionHeight < area.Height;
        }

        private void Fade()
        {
            //Set the animation to the start.
            fadePosition = 0;

            //do the first frame of the animation process.
            Animate();

            //Start the timer to complete the rest of the frames.
            fadeTimer.Start();
        }

        public event EventHandler DrawInvalidated;

        private void ReDraw()
        {
            if (DrawInvalidated != null)DrawInvalidated(this, EventArgs.Empty);
            Invalidate();
        }

        private Bitmap backgroundCanvas = null;

        private void InvalidateBackground()
        {
            if (backgroundCanvas != null)
            {
                backgroundCanvas.Dispose();
                backgroundCanvas = null;                
            }
        }

        public void Draw(System.Drawing.Bitmap canvas, System.Drawing.Rectangle area)
        {
            Graphics gCanvas = Graphics.FromImage(canvas);

            if (backgroundCanvas != null && (backgroundCanvas.Width != area.Width || backgroundCanvas.Height != area.Height))
                InvalidateBackground();

            if (backgroundCanvas == null)
            {
                backgroundCanvas = new Bitmap(area.Width, area.Height, gCanvas);
                Graphics gBackgroundCanvas = Graphics.FromImage(backgroundCanvas);

                //Paint the background.
                if (DisplayStyle.BackgroundTransparent)
                {
                    gBackgroundCanvas.FillRectangle(new SolidBrush(Color.Transparent), area);
                }
                else if(File.Exists(DisplayStyle.BackgroundImage))
                {
                    Bitmap background = new Bitmap(DisplayStyle.BackgroundImage);
                    gBackgroundCanvas.DrawImage(background, area);
                    background.Dispose();
                }
                else
                {
                    gBackgroundCanvas.FillRectangle(new SolidBrush(DisplayStyle.BackgroundColour), area);
                } 
            }

            gCanvas.CompositingMode = CompositingMode.SourceCopy;
            gCanvas.DrawImageUnscaled(backgroundCanvas, new Point(0, 0));
            gCanvas.CompositingMode = CompositingMode.SourceOver;               

            OnPaint(new PaintEventArgs(gCanvas,area));
        }

        private TextEffectRenderer oldWordsRenderer = new TextEffectRenderer();
        private TextEffectRenderer newWordsRenderer = new TextEffectRenderer();
        private TextEffectRenderer copyrightRenderer = new TextEffectRenderer();

        protected override void OnPaint(PaintEventArgs e)
        {
            RectangleF displayArea;

            displayArea = MainDisplayArea;

            //Setup Old Text Renderer
            oldWordsRenderer.Font = DisplayStyle.MainText.ScaledFont(this.Height);
            oldWordsRenderer.ForeColor = DisplayStyle.MainText.ForeColour;
            oldWordsRenderer.Opacity = 1.0f - fadePosition;
            oldWordsRenderer.Alignment = DisplayStyle.MainText.Alignment;
            oldWordsRenderer.TextOutline = DisplayStyle.MainText.TextOutline;
            oldWordsRenderer.DropShadow = DisplayStyle.MainText.DropShadow;

            newWordsRenderer.Font = DisplayStyle.MainText.ScaledFont(this.Height);
            newWordsRenderer.ForeColor = DisplayStyle.MainText.ForeColour;
            newWordsRenderer.Opacity = fadePosition;
            newWordsRenderer.Alignment = DisplayStyle.MainText.Alignment;
            newWordsRenderer.TextOutline = DisplayStyle.MainText.TextOutline;
            newWordsRenderer.DropShadow = DisplayStyle.MainText.DropShadow;

            copyrightRenderer.Font = DisplayStyle.Copyright.ScaledFont(this.Height);
            copyrightRenderer.ForeColor = DisplayStyle.Copyright.ForeColour;
            copyrightRenderer.Alignment = DisplayStyle.Copyright.Alignment;

            //Draw the main Text
            oldWordsRenderer.Draw(oldWords, displayArea, e.Graphics);
            newWordsRenderer.Draw(currentWords, displayArea, e.Graphics);

            //Draw the copyright notice
            displayArea = DisplayStyle.Copyright.Margin.ResizeRectangle(new RectangleF(0, 0, this.Width, this.Height));
            copyrightRenderer.Draw(Copyright, displayArea, e.Graphics);
        }

        public RectangleF MainDisplayArea
        {
            get 
            {
                return DisplayStyle.MainText.Margin.ResizeRectangle(new RectangleF(0, 0, this.Width, this.Height));
            }
        }
	

        private void ConvertAlignment(ContentAlignment align, ref StringFormat format)
        {
            switch (align)
            {
                case ContentAlignment.TopLeft:
                    format.Alignment = StringAlignment.Near;
                    format.LineAlignment = StringAlignment.Near;
                    break;
                case ContentAlignment.TopCenter:
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Near;
                    break;
                case ContentAlignment.TopRight:
                    format.Alignment = StringAlignment.Far;
                    format.LineAlignment = StringAlignment.Near;
                    break;
                case ContentAlignment.MiddleLeft:
                    format.Alignment = StringAlignment.Near;
                    format.LineAlignment = StringAlignment.Center;
                    break;
                case ContentAlignment.MiddleCenter:
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;
                    break;
                case ContentAlignment.MiddleRight:
                    format.Alignment = StringAlignment.Far;
                    format.LineAlignment = StringAlignment.Center;
                    break;
                case ContentAlignment.BottomLeft:
                    format.Alignment = StringAlignment.Near;
                    format.LineAlignment = StringAlignment.Far;
                    break;
                case ContentAlignment.BottomCenter:
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Far;
                    break;
                case ContentAlignment.BottomRight:
                    format.Alignment = StringAlignment.Far;
                    format.LineAlignment = StringAlignment.Far;
                    break;

                default:
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;
                    break;
            }
        }
	
    }
}
